import torch
from torch import nn
from d2l import torch as d2l

# def corr2d(X,K):
#     """计算二维互相关运算"""
#     h,w = K.shape
#     Y = torch.zeros((X.shape[0]-h+1,X.shape[1]-w+1))
#     for i in range(Y.shape[0]):
#         for j in range(Y.shape[1]):
#             Y[i,j] = (X[i:i+h,j:j+w]*K).sum()
#     return Y
#
# class Conv2D(nn.Module):
#     def __init__(self,kernel_size):
#         super().__init__()
#         self.weight = nn.Parameter(torch.rand(kernel_size))
#         self.bias = nn.Parameter(torch.zeros(1))
#
#     def forward(self, x):
#         return corr2d(x,self.weight)+self.bias
#
# X = torch.ones((6,8))
# X[:,2:6]=0
# print(X)
#
# K = torch.tensor([[1.0,-1.0]])
# Y=corr2d(X,K)
#
# conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)
#
# X = X.reshape((1,1,6,8))
# Y = Y.reshape((1,1,6,7))
#
# for i in range(100):
#     Y_hat = conv2d(X)
#     l = (Y_hat-Y)**2
#     conv2d.zero_grad()
#     l.sum().backward()
#     conv2d.weight.data[:] -= 3e-2*conv2d.weight.grad
#     if (i+1)%2 ==0:
#         print(f"batch{i+1},loss{l.sum():.3f}")
#
# print(conv2d.weight.data.reshape((1,2)))

# def comp_conv2d(conv2d,X):
#     X = X.reshape((1,1)+X.shape) # 拉成四维
#     Y = conv2d(X)
#     return Y.reshape(Y.shape[2:]) # 返回后两维
#
# conv2d = nn.Conv2d(1,1,kernel_size=3,padding=1,stride=2)
# X = torch.rand(size=(8,8))
# print(comp_conv2d(conv2d,X).shape)

# def corr2d_multi_in(X,K):
#     return sum(d2l.corr2d(x,k) for x,k in zip(X,K))
#
# X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
#                [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
# K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
#
# print(corr2d_multi_in(X, K))


class Reshape(torch.nn.Module):
    def forward(self,x):
        return x.view(-1,1,28,28)

net = nn.Sequential(
    Reshape(),nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2,stride=2),
    nn.Conv2d(6,16,kernel_size=5),nn.ReLU(),
    nn.AvgPool2d(kernel_size=2,stride=2),nn.Flatten(),
    nn.Linear(16*5*5,120),nn.ReLU(),
    nn.Linear(120,84),nn.Sigmoid(),
    nn.Linear(84,10))

X = torch.rand(size=(1,1,28,28),dtype=torch.float32)
for layer in net:
    X = layer(X)
    print(f"{layer.__class__.__name__},output shape  {X.shape}")

batch_size = 256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

def evaluate_accuracy_gpu(net,data_iter,device=None):
    if isinstance(net,torch.nn.Module):
        net.eval()   # eval模式不会改变参数的值，用来预测结果
        if not device:
            device = next(iter(net.parameters())).device
    metric = d2l.Accumulator(2)
    for X,y in data_iter:
        if isinstance(X,list):
            X = [x.to(device) for x in X]
        else:
            X = X.to(device)
        y = y.to(device)
        metric.add(d2l.accuracy(net(X),y),y.numel())
    return metric[0]/metric[1]

#@save
#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
    """用GPU训练模型(在第六章定义)"""
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on', device)
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = d2l.Timer(), len(train_iter)
    for epoch in range(num_epochs):
        # 训练损失之和，训练准确率之和，样本数
        metric = d2l.Accumulator(3)
        net.train()
        for i, (X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            with torch.no_grad():
                metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (train_l, train_acc, None))
        test_acc = evaluate_accuracy_gpu(net, test_iter)
        animator.add(epoch + 1, (None, None, test_acc))
    print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
          f'test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(device)}')

lr,num_epochs= 0.9,10
train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
d2l.plt.show()